#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <math.h>
#include "Game.h"
#include "Thread.hpp"

char		_running = 0;

typedef void*	(*ThreadFct)(void*);

void*		startKeyManaging(void* ag)
{
  Game*		game = static_cast<Game*>(ag);

  while (game->manage_keys());
  std::cout << "END.\n";
  _running = 2;
  return NULL;
}

typedef struct		elm
{
  float			vx;
  float			vy;
  float			x;
  float			y;
  float			width;
  Uint32		color;
  // Pas top mais mieux que de decomposer color pour recuperer les canaux
  Uint8			red;
  Uint8			green;
  Uint8			blue;
}			Elm;

int		main(int ac, char** av)
{
  srand(time(NULL));
  std::cout << "Loading";

  if (ac < 3)
    {
      std::cout << "Usage: cmd <maxObjs> <fadeCycles>" << std::endl;
      return 0;
    }

  Game		g(1);
  int		nbObj = randuint(atoi(av[1])) + 2;
  int		fadeCycles = atoi(av[2]);
  if (fadeCycles < 1)
    fadeCycles = 1;
  int		fade = fadeCycles;
  Elm*		elms = new Elm[nbObj];
  unsigned int	_sleep_time = 0;

  _running = 1;
   if (ac > 2)
    _sleep_time = atoi(av[2]);
  std::cout << std::endl;
  g.init_SDL();

  std::cout.setf(std::ios::fixed, std::ios::floatfield);
  std::cout.precision(10);

  AThread	thread;
  thread.start((Routine)(startKeyManaging), static_cast<void*>(&g));


  // RANDOMISATION DES VALEURS DE DEPART
  for (int i = 0; i < nbObj; ++i)
    {
      elms[i].x = randuint(_xdim - 1);
      elms[i].y = randuint(_ydim - 1);
      elms[i].vx = 0;//(randuint(1) -2.0);
      elms[i].vy = 0;//(randuint(1) -2.0);
      //@param: randuint(7) + 3;
      elms[i].width = randuint(7) + 3;
      elms[i].red = randuint(150);
      elms[i].green = randuint(150);
      elms[i].blue = randuint(150);
      elms[i].color = SDL_MapRGB(SDL_GetVideoSurface()->format,
				 elms[i].red, elms[i].green, elms[i].blue);
    }
  // CREATION DU MASQUE PROGRESSIF
  SDL_Surface*  mask = SDL_CreateRGBSurface(SDL_SWSURFACE, _xdim, _ydim, 32,0,0,0,0);
  SDL_Surface*  curves = SDL_CreateRGBSurface(SDL_SWSURFACE, _xdim, _ydim, 32,0,0,0,0);
  SDL_Surface*  balls = SDL_CreateRGBSurface(SDL_SWSURFACE, _xdim, _ydim, 32,0,0,0,0);
  SDL_Rect*	bufferRect = new SDL_Rect();
  bufferRect->x = 0;
  bufferRect->y = 0;
  bufferRect->w = _xdim;
  bufferRect->h = _ydim;
  SDL_FillRect(mask, bufferRect, SDL_MapRGB(SDL_GetVideoSurface()->format, 0, 0, 0));
  SDL_FillRect(balls, bufferRect, SDL_MapRGB(SDL_GetVideoSurface()->format, 0, 0, 0));
  SDL_FillRect(curves, bufferRect, SDL_MapRGB(SDL_GetVideoSurface()->format, 0, 0, 0));
  SDL_SetAlpha(mask, SDL_SRCALPHA, 1);
  SDL_SetColorKey(balls, SDL_SRCCOLORKEY, 0); 

  // BOUCLE P
  while (_running != 2)
    {
      for (int i = 0; i < nbObj; ++i)
	{
	  for (int j = 0; j < nbObj; ++j)
	    {
	      if (i != j)
		{
		  // CALCUL DES FORCES
		  float	vfx = elms[j].x - elms[i].x; // Vector force en X
		  float	vfy = elms[j].y - elms[i].y;
		  // esquive rotative du "divide by zero"
		  float	angle = atan2(vfy, (vfx == 0 ? 0.00000001f : vfx));
		  float	vfux = cos(angle); // Vector force unitaire en X
		  float	vfuy = sin(angle);
		  //float	force = elms[j].width/((vfx*vfx) + (vfy*vfy)); //Dist Ponderee
		  float		pythaOverFlow = (vfx*vfx) + (vfy*vfy);
		  /*
		  if (isinf(pythaOverFlow))
		    pythaOverFlow = 100000;
		  */
		  float		dist = sqrt(pythaOverFlow);
		  float		force;
		  // @param: courbure espace (dist/param)
		  
		  force = (elms[j].width*exp(-1.0*dist/150.0))/100.0;
		  // original working calculation: (elms[j].width*exp(-1.0*dist/100.0))/100.0;
		  // original true calculation: force = elms[j].width/(dist*dist);
		  // BONUS: LA MATIERE SARRACHE
		  float		absorbDist = 0.1*elms[i].width;
		  if (absorbDist < 1)
		    absorbDist = 1;
		  // @param: aborb dist 0.1
		  if (0.1*elms[i].width > dist && elms[i].width > elms[j].width && elms[j].width != 0.0)
		    {
		      elms[i].vx *= (1-(elms[j].width/elms[i].width));
		      elms[i].vy *= (1-(elms[j].width/elms[i].width));
		      //elms[j].vy *= (1-(elms[i].width/elms[j].width));
		      //elms[j].vy *= (1-(elms[i].width/elms[j].width));
		      float	explosionSize = elms[j].width * 100;
		      float	es1 = elms[j].width;
		      float	es2 = elms[j].width * 5;
		      float	es3 = elms[j].width * 2;
		      for (int k = 0; k < explosionSize; ++k)
			{
			  float eAngle = randuint(360)*0.0174532925;
			  int	rDist = randuint(11);
			  float eForce; 
			  if (rDist < 5)
			    eForce = randuint(es1);
			  else if (rDist < 8)
			    eForce = randuint(es2);
			  else if (rDist < 10)
			    eForce = randuint(es3);
			  else
			    eForce = es3 + randuint(10);
			  float xpos = elms[j].x + eForce*cos(eAngle);
			  float ypos = elms[j].y + eForce*sin(eAngle);
			  if (g.belongs(xpos, ypos))
			    g.dot32(xpos, ypos, elms[j].color);
			}
		      ++elms[i].width;
		      --elms[j].width;
		    }
		  // END BONUS
		  elms[i].vx += vfux * force;
		  elms[i].vy += vfuy * force;

		  // CURVES AT BACK
		  for (int c = 0; c < 10; ++c)
		    {
		      int	dc = c*18;
		      int	rs = elms[i].red - dc;
		      int	gs = elms[i].green - dc;
		      int	bs = elms[i].blue - dc;
		      if (rs < 0) rs = 0;
		      if (gs < 0) gs = 0;
		      if (bs < 0) bs = 0;
		      float	xs = _xdim/2 + cos(angle)*(10000*force + c*exp(force*100)/4);
		      float	ys = _ydim/2 + sin(angle)*(10000*force + c*exp(force*100)/4);
		      // si la couleur est plus claire avant, garder, sinon afficher new
		      if (g.belongs(xs, ys))
			g.maxDot32(curves, xs, ys, SDL_MapRGB(SDL_GetVideoSurface()->format, rs, gs, bs));
		    }
		  /*	  		  
		  std::cout << "Elm " << i << " - modificator " << j << " : " <<std::endl;
		  std::cout << "vfx = " << vfx << std::endl;
		  std::cout << "vfy = " << vfy << std::endl;
		  std::cout << "angle = " << angle << std::endl;
		  std::cout << "dist = " << dist << std::endl;
		  
		  std::cout << "force " << force
			    << " resultantSpeed Vx = " << elms[i].vx 
			    << " resultantSpeed Vy = " << elms[i].vy << std::endl;
		  if (isinf(dist))
		  std::cout << "Should actually throw away within such values!" << std::endl;*/
		}
	    }
	  while (g.paused() && !(_running == 2))
	    {
	      usleep(100);
	    }
	}

      for (int i = 0; i < nbObj; ++i)
	{
	  // MAJ DES COORDONNEES
	  elms[i].vx *= 0.99999999;
	  elms[i].vy *= 0.99999999;
	    //@param: 0.10=simulation speed
	  elms[i].x += 0.10*elms[i].vx;
	  elms[i].y += 0.10*elms[i].vy;
	  /*test here
	    if (elms[i].vx > 2)
	    elms[i].vx = 2;
	    if (elms[i].vx < -2)
	    elms[i].vx = -2;
	    if (elms[i].vy > 2)
	    elms[i].vy = 2;
	    if (elms[i].vy < -2)
	    elms[i].vy = -2;
	    end test*/
	  // REBONDS
	  if (elms[i].x < 0.0 || elms[i].x > _xdim)
	    elms[i].vx = -elms[i].vx;
	  if (elms[i].y < 0.0 || elms[i].y > _ydim)
	    elms[i].vy = -elms[i].vy;
	  //std::cout << "i" << i << ":" << elms[i].vx << "," << elms[i].vy << std::endl;

	  if (elms[i].width != 0)
	    {
	      float		step = 1 + 360.0/(elms[i].width*6.283185306);
	      for (int r = 0; r < 360; r += step)
		{
		  // std::cout << "step " << step << std::endl;
		  float	posx = elms[i].x + elms[i].width * cos(r*0.0174532925);
		  float	posy = elms[i].y + elms[i].width * sin(r*0.0174532925);
		  if (g.belongs(posx, posy))
		    g.dot32(balls, posx, posy, elms[i].color);
		}
	      if (g.belongs(elms[i].x, elms[i].y))
		g.dot32(balls, elms[i].x, elms[i].y, elms[i].color);
	    }
	  //std::cout << "==============================================" << std::endl;
	}

      /*      for (int x = 0; x < _xdim; ++x)
	for (int y = 0; y < _ydim; ++y)
	  g.dot32(x, y, SDL_MapRGB(SDL_GetVideoSurface()->format, 0, 0, 0, 0.01));
      */
      if (_sleep_time)
	usleep(_sleep_time);
      g.sync();
      // APPLICATION DU MASQUE NOIR PROGRESSIF
      if ((--fade) == 0)
	{
	  fade = fadeCycles;
	  SDL_BlitSurface(mask, bufferRect, balls, bufferRect);
	}

      SDL_BlitSurface(curves, bufferRect, g.getScreen(), bufferRect);
      /*
      for (int xb = 0; xb < _xdim; ++xb)
	for (int yb = 0; yb < _ydim; ++yb)
	  g.maxDot32(g.getScreen(), xb, yb, g.getDot32(balls, xb, yb));
      */
      SDL_BlitSurface(balls, bufferRect, g.getScreen(), bufferRect);
      //      g.cls();
      if (_verb)
	std::cout << "-----------------Refreshed-----------------" << std::endl;
    }
  return 0;
}
